//
//  DMAController.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 26/10/2019.
//  Copyright © 2019 Thomas Harte. All rights reserved.
//

#pragma once

#include "ClockReceiver/ClockReceiver.hpp"
#include "ClockReceiver/ClockingHintSource.hpp"
#include "Components/1770/1770.hpp"
#include "Activity/Source.hpp"

#include <cstdint>
#include <vector>

namespace Atari::ST {

class DMAController: public WD::WD1770::Delegate, public ClockingHint::Source, public ClockingHint::Observer {
public:
	DMAController();

	uint16_t read(int address);
	void write(int address, uint16_t value);
	void run_for(HalfCycles duration);

	bool get_interrupt_line();
	bool get_bus_request_line();

	/*!
		Indicates that the DMA controller has been granted bus access to the block of memory at @c ram, which
		is of size @c size.

		@returns The number of words read or written.
	*/
	int bus_grant(uint16_t *ram, size_t size);

	void set_floppy_drive_selection(bool drive1, bool drive2, bool side2);
	void set_floppy_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);

	struct Delegate {
		virtual void dma_controller_did_change_output(DMAController &) = 0;
	};
	void set_delegate(Delegate *delegate);

	void set_activity_observer(Activity::Observer *observer);

	// ClockingHint::Source.
	ClockingHint::Preference preferred_clocking() const final;

private:
	HalfCycles running_time_;
	struct WD1772: public WD::WD1770 {
		WD1772(): WD::WD1770(WD::WD1770::P1772) {
			emplace_drives(2, 8000000, 300, 2);
			set_is_double_density(true);	// TODO: is this selectable on the ST?
		}

		void set_motor_on(bool motor_on) final {
			for_all_drives([motor_on] (Storage::Disk::Drive &drive, size_t) {
				drive.set_motor_on(motor_on);
			});
		}

		void set_floppy_drive_selection(bool drive1, bool drive2, bool side2) {
			set_drive(
				(drive1 ? 1 : 0) |
				(drive2 ? 2 : 0)
			);

			for_all_drives([side2] (Storage::Disk::Drive &drive, size_t) {
				drive.set_head(side2);
			});
		}

		void set_activity_observer(Activity::Observer *observer) {
			get_drive(0).set_activity_observer(observer, "Internal", true);
			get_drive(1).set_activity_observer(observer, "External", true);
		}

		void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
			get_drive(drive).set_disk(disk);
		}

	} fdc_;

	void wd1770_did_change_output(WD::WD1770 &) final;

	uint16_t control_ = 0;

	Delegate *delegate_ = nullptr;
	bool interrupt_line_ = false;
	bool bus_request_line_ = false;

	void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) final;

	// MARK: - DMA State.
	struct Buffer {
		uint8_t contents[16];
		bool is_full = false;
	} buffer_[2];
	int active_buffer_ = 0;
	int bytes_received_ = 0;
	bool error_ = false;
	int address_ = 0;
	int byte_count_ = 0;
};

}
